− 4 -4 − 4 0 0 0 − 4 -4 − 4 12 12 1 2 
− 3 -3 − 3 维护可重集中求互质的数字对数, 我们开桶模拟可重集, 然后记每个不含平方因子的数的倍数数量, 这样就可以进行容斥算出每次插入或删除的数的互质数量, 得到答案的变化量. 我们枚举每个因数作为公因数计算数字的对数, 容斥系数显然是莫比乌斯函数. 复杂度 O ( n n ) O(n\sqrt n) O ( n n  ) 
这个题有上个题的影子. 对于静态问题, 我们做一个特殊的树上背包, 按路径 GCD 记录所有的路径, 然后用一个辅助数组在合并背包的同时统计贡献, 直接按上个题的思想进行合并即可. 复杂度 O ( n 2 a ) O(n^2 \sqrt a) O ( n 2 a  ) 
对于修改, 我们可以每次计算修改的边在修改前对答案的贡献, 同样是 O ( n 2 ) O(n^2) O ( n 2 ) O ( n a ) O(n\sqrt a) O ( n a  ) 
因此复杂度是 O ( ( n + q ) n a ) O((n + q)n\sqrt a) O ( ( n + q ) n a  ) 7 0 ′ 70' 7 0 ′ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 inline  unsigned  Gcd (unsigned  x, unsigned  y)    unsigned  TmpG;   while  (y) TmpG = x, x = y, y = TmpG % y;   return  x; } bitset<1000005> No; struct  Node ;struct  Edge  {  Node *To[2 ];   unsigned  Val; }Ed[100005 ]; unsigned  Pri[100005 ], Cnt[1000005 ], TmpP[1000005 ];unsigned  Stack[1000005 ], *STop (Stack);unsigned  long  long  Ans (0 ) , Tmp (0 ) unsigned  m, n, CntP (0 );unsigned  A, B, C, D, t;struct  Node  {  vector <Edge*> E;   map<unsigned , unsigned > Val;   inline  void  DFS (Edge* Fa)       Val.clear ();     unordered_map<unsigned , unsigned > TmpM;     for  (auto  i:E) if (i != Fa){       Node*Cur (i->To[i->To[0 ] == this ])  ;       Cur->DFS (i);       for  (auto  j:Cur->Val) {         unsigned  G (Gcd(j.first, i->Val))          if (G == 1 ) Tmp += j.second;         for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){           if (Cnt[k] < 0x3f3f3f3f ) {             if (Cnt[k] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpM[k];             else  Tmp += (unsigned  long  long )j.second * TmpM[k];           }           unsigned  ToG (G / k)            if (Cnt[ToG] < 0x3f3f3f3f  && (k ^ ToG)) {             if (Cnt[ToG] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpM[ToG];             else  Tmp += (unsigned  long  long )j.second * TmpM[ToG];           }         }       }       for  (auto  j:Cur->Val) {         unsigned  G (Gcd(j.first, i->Val))          Val[G] += j.second;         for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){           unsigned  ToG (G / k)            if (Cnt[k] < 0x3f3f3f3f ) TmpM[k] += j.second;            if (Cnt[ToG] < 0x3f3f3f3f  && (ToG ^ k)) TmpM[ToG] += j.second;         }       }     }     if (Fa) ++Val[Fa->Val];   }   inline  void  DFS2 (Edge* Fa)       Val.clear ();     for  (auto  i:E) if (i != Fa){       Node*Cur (i->To[i->To[0 ] == this ])  ;       Cur->DFS2 (i);       for  (auto  j:Cur->Val) Val[Gcd (j.first, i->Val)] += j.second;     }     if (Fa) ++Val[Fa->Val];   } }N[100005 ]; signed  main ()    Cnt[1 ] = 0 , Cnt[0 ] = 0 ;   for  (unsigned  i (2 ); i <= 1000000 ; ++i) {     if (!No[i]) Pri[++CntP] = i, Cnt[i] = 1 ;     for  (unsigned  j (1 ), k (i << 1 ); (k <= 1000000 ) && (j <= CntP); ++j, k = Pri[j] * i) {       No[k] = 1 , Cnt[k] = Cnt[i] + 1 ;       if (!(i % Pri[j])) Cnt[k] = 0x3f3f3f3f ;     }   }   n = RD ();   for  (unsigned  i (1 ); i < n; ++i) {     Ed[i].To[0 ] = N + (A = RD ());     Ed[i].To[1 ] = N + (B = RD ());     Ed[i].Val = RD ();     N[A].E.push_back (Ed + i);     N[B].E.push_back (Ed + i);   }   N[1 ].DFS (NULL ), Ans = Tmp;   printf ("%llu\n" , Ans);   m = RD ();   for  (unsigned  i (1 ); i <= m; ++i) {     A = RD (), B = RD (), Tmp = 0 ;     Ed[A].To[0 ]->DFS2 (Ed + A);     Ed[A].To[1 ]->DFS2 (Ed + A);     --(Ed[A].To[1 ]->Val[Ed[A].Val]);     for  (auto  j:Ed[A].To[0 ]->Val) {       unsigned  G (Gcd(Ed[A].Val, j.first))        if (G == 1 ) Tmp += j.second;       for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){         unsigned  ToG (G / k)          if (Cnt[k] < 0x3f3f3f3f ) TmpP[k] += j.second, *(++STop) = k;         if (Cnt[ToG] < 0x3f3f3f3f  && (ToG ^ k)) TmpP[ToG] += j.second, *(++STop) = ToG;       }     }     for  (auto  j:Ed[A].To[1 ]->Val) {       unsigned  G (j.first)        for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){         if (Cnt[k] < 0x3f3f3f3f ) {           if (Cnt[k] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpP[k];           else  Tmp += (unsigned  long  long )j.second * TmpP[k];         }         if (Cnt[G / k] < 0x3f3f3f3f  && ((k * k) ^ G)) {           if (Cnt[G / k] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpP[G / k];           else  Tmp += (unsigned  long  long )j.second * TmpP[G / k];         }       }     }     --(Ed[A].To[0 ]->Val[Ed[A].Val]);     ++(Ed[A].To[0 ]->Val[Ed[A].Val = B]);     Ans -= Tmp, Tmp = 0 ;     while  (STop > Stack) TmpP[*(STop--)] = 0 ;     for  (auto  j:Ed[A].To[0 ]->Val) {       unsigned  G (Gcd(Ed[A].Val, j.first))        if (G == 1 ) Tmp += j.second;       for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){         unsigned  ToG (G / k)          if (Cnt[k] < 0x3f3f3f3f ) TmpP[k] += j.second, *(++STop) = k;         if (Cnt[ToG] < 0x3f3f3f3f  && (ToG ^ k)) TmpP[ToG] += j.second, *(++STop) = ToG;       }     }     for  (auto  j:Ed[A].To[1 ]->Val) {       unsigned  G (j.first)        for  (unsigned  k (sqrt (G)); k; --k) if (!(G % k)){         if (Cnt[k] < 0x3f3f3f3f ) {           if (Cnt[k] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpP[k];           else  Tmp += (unsigned  long  long )j.second * TmpP[k];         }         if (Cnt[G / k] < 0x3f3f3f3f  && ((k * k) ^ G)) {           if (Cnt[G / k] & 1 ) Tmp -= (unsigned  long  long )j.second * TmpP[G / k];           else  Tmp += (unsigned  long  long )j.second * TmpP[G / k];         }       }     }     while  (STop > Stack) TmpP[*(STop--)] = 0 ;     Ans += Tmp;     printf ("%llu\n" , Ans);   }   return  Wild_Donkey; } 
接下来考虑正解. 按照前面的思路, 我们对于所有 i i i i i i 
我们考虑枚举所有的数作为公因数, 找出每个连通块求出大小, 如果一个连通块的大小为 x x x x ( x − 1 ) 2 \frac {x(x - 1)}2 2 x ( x − 1 )  O ( n a log  n ) O(n\sqrt a\log n) O ( n a  log  n ) 
因为有平方因子的因数的 μ \mu μ 0 0 0 1 0 6 10^6 1 0 6 6 6 6 64 64 6 4 O ( 64 n log  n ) O(64n\log n) O ( 6 4 n log  n ) 
对于动态问题, 我们离线所有询问, 对于每个公因数单独计算贡献, 计算时先求出全程没有变化的边的局面, 然后枚举每个时刻计算有变化的边的贡献. 复杂度 O ( 64 ( Q 2 + n ) log  n ) O(64(Q^2 + n)\log n) O ( 6 4 ( Q 2 + n ) log  n ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 bitset<1000005> No; unsigned  Pri[100005 ], CntP (0 ), Cnt[1000005 ], Small[1000005 ];struct  Node  {  Node* Fa; unsigned  Size; }N[100005 ]; pair<Node, Node*> Stack[200005 ], *STop (Stack), *SBot; struct  Edge  {  Node *To[2 ]; }E[100005 ]; struct  Change  {  Edge* Ed;   unsigned  Range[2 ], Val;   inline  const  char  operator <(const  Change& x) const  { return  Range[0 ] < x.Range[0 ]; } }Edit[205 ], *CntE (Edit); vector<Edge*> List[1000005 ]; vector<Change*> Added[1000005 ]; unsigned  Val[100005 ], Tot[65 ];unsigned  long  long  Ans[105 ], Tmp (0 ), LTmp (0 );unsigned  m, n, A, B, C, D;inline  unsigned  Insert (unsigned  y)    unsigned  Cl (0 ) , TmP   Tot[0 ] = 1 ;   while  (y > 1 ) {     TmP = Small[y];     while  (!(y % TmP)) y /= TmP;     Tot[1  << (Cl++)] = TmP;   }   for  (unsigned  j (1 ), k (2 ); j < Cl; ++j, k <<= 1 )     for  (unsigned  l ((1  << j) - 1 ); l; --l) Tot[k | l] = Tot[k] * Tot[l];   return  (1  << Cl) - 1 ;? } inline  Node* Find (Node* x)  while  (x->Fa != x) x = x->Fa; return  x;}inline  unsigned  long  long  Calc (unsigned  x)  return  ((unsigned  long  long )x * (x - 1 )) >> 1 ;}inline  void  Add (Edge* x)    Node *LS (Find(x->To[0 ])) , *RS (Find(x->To[1 ]))  ;   *(++STop) = {*LS, LS}, *(++STop) = {*RS, RS};   if (LS->Size < RS->Size) swap (LS, RS);   Tmp -= Calc (LS->Size), Tmp -= Calc (RS->Size);    LS->Size += RS->Size, RS->Fa = LS;   Tmp += Calc (LS->Size); } signed  main ()    Cnt[1 ] = 0 , Cnt[0 ] = 0 , Small[1 ] = 1 ;   for  (unsigned  i (2 ); i <= 1000000 ; ++i) {     if (!No[i]) Pri[++CntP] = i, Cnt[i] = 1 , Small[i] = i;     for  (unsigned  j (1 ), k (i << 1 ); (k <= 1000000 ) && (j <= CntP); ++j, k = Pri[j] * i) {       No[k] = 1 , Cnt[k] = Cnt[i] + 1 , Small[k] = Pri[j];       if (!(i % Pri[j])) Cnt[k] = 0x3f3f3f3f ;     }   }   n = RD ();   for  (unsigned  i (1 ); i < n; ++i) E[i].To[0 ] = N + RD (), E[i].To[1 ] = N + RD (), Val[i] = RD ();   m = RD ();   map<Edge*, Change*> Last;   for  (unsigned  i (1 ); i <= m; ++i) {     A = RD ();     if (Val[A] < 0x3f3f3f3f ) *(++CntE) = {E + A, 0 , 0 , Val[A]}, Val[A] = 0x3f3f3f3f , Last[E + A] = CntE;     *(++CntE) = {E + A, i, 0 , RD ()}, Last[E + A]->Range[1 ] = i - 1 , Last[E + A] = CntE;    }   for  (auto  i:Last) { i.second->Range[1 ] = m; }   for  (Change* i (CntE); i > Edit; --i) {     for  (unsigned  j (Insert (i->Val)); ~j; --j) Added[Tot[j]].push_back (i);   }   for  (unsigned  i (1 ); i < n; ++i) if (Val[i] < 0x3f3f3f3f )     for  (unsigned  j (Insert (Val[i])); ~j; --j) List[Tot[j]].push_back (E + i);   for  (unsigned  i (1 ); i <= n; ++i) N[i].Fa = N + i, N[i].Size = 1 ;    for  (unsigned  i (1 ); i <= 1000000 ; ++i) if (Cnt[i] < 0x3f3f3f3f ) {     Tmp = 0 ;     while  (STop > Stack) *(STop->second) = STop->first, --STop;     for  (auto  j:List[i]) Add (j); LTmp = Tmp, SBot = STop;     for  (unsigned  j (0 ); j <= m; ++j) {       Tmp = LTmp;       while  (STop > SBot) *(STop->second) = STop->first, --STop;       for  (auto  k:Added[i]) if ((k->Range[0 ] <= j) && (j <= k->Range[1 ])) Add (k->Ed);       if (Cnt[i] & 1 ) Ans[j] -= Tmp;       else  Ans[j] += Tmp;     }   }   for  (unsigned  i (0 ); i <= m; ++i) printf ("%llu\n" , Ans[i]);   return  Wild_Donkey; } 
− 2 -2 − 2 历史总是惊人地相似, 我再次起到了 12 12 1 2 
首先容易发现任何情况下原图中不在一个强连通分量中的点不会产生贡献, 所以问题转化为将所有强连通分量之间的边删掉而仅考虑强连通分量内的问题.
一开始想的是对于只求 h ( G ) h(G) h ( G ) 
但是注意到每次删除边或加入边, 树的形态都可以出现巨大变化, 所以不能维护. 我们发现点数只有 1000 1000 1 0 0 0 h h h a a a ( i , j ) (i, j) ( i , j ) min  ( a i , j , a j , i ) ≥ i \min(a_{i, j}, a_{j, i}) \geq i min ( a i , j  , a j , i  ) ≥ i 
做一次是 O ( n 3 ) O(n^3) O ( n 3 ) O ( n 3 ) O(n^3) O ( n 3 ) O ( m n 3 ) O(mn^3) O ( m n 3 ) 4 4 ′ 44' 4 4 ′ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 unsigned  a[1005 ][1005 ], Sid[200005 ][2 ], Prt[200005 ], m, n;unsigned  A, B, C, D, t;unsigned  Cnt (0 ) , Ans (0 ) , Tmp (0 ) inline  void  Udt (unsigned  x, unsigned  y)    unsigned  Cur (a[x][y]) , To   for  (unsigned  j (1 ), k (min (j, y)); j <= n; ++j, k = min (j, y)) if (a[j][y] < (To = min (a[j][x], Cur)))     {if (min (a[j][y], a[y][j]) < k && min (a[y][j], To) >= k) ++Ans; a[j][y] = To, Udt (j, y);}   for  (unsigned  j (1 ), k (min (j, x)); j <= n; ++j, k = min (j, x)) if (a[x][j] < (To = min (a[y][j], Cur)))     {if (min (a[x][j], a[j][x]) < k && min (a[j][x], To) >= k) ++Ans; a[x][j] = To, Udt (x, j);} } signed  main ()    Ans = n = RD (), m = RD ();   for  (unsigned  i (1 ); i <= m; ++i) Sid[i][0 ] = RD (), Sid[i][1 ] = RD ();   for  (unsigned  i (1 ); i <= n; ++i) a[i][i] = i;   Prt[m + 1 ] = Ans;   for  (unsigned  i (m); i; --i) {     printf ("%u:\n" , i);      A = Sid[i][0 ], B = Sid[i][1 ], C = min (A, B);     if (min (a[A][B], a[B][A]) < C && min (a[B][A], C) >= C) ++Ans;     a[A][B] = max (a[A][B], C), Udt (A, B), Prt[i] = Ans;     for  (unsigned  j (1 ); j <= n; ++j) {       for  (unsigned  k (1 ); k <= n; ++k) printf ("%2u" , a[j][k]); putchar (0x0A );     }   }   for  (unsigned  i (0 ); i <= m; ++i) printf ("%u " , Prt[i + 1 ]);   putchar (0x0A );   return  Wild_Donkey; } 
我们发现, 可以把问题转化为对每个有序点对 ( i , j ) (i, j) ( i , j ) min  ( i , j ) \min(i, j) min ( i , j ) ( i , j ) (i, j) ( i , j ) 
所以我们可以记邻接矩阵 a i , j a_{i, j} a i , j  m i n i , j min_{i, j} m i n i , j  i i i j j j O ( n 3 + m ) O(n^3 + m) O ( n 3 + m ) -O2, 因此简单卡常即可 AC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned  a[1005 ][1005 ], Prt[200005 ], m, n;unsigned  A, B, C, D, t;unsigned  Cnt (0 ) , Ans (0 ) , Tmp (0 ) signed  main ()    n = RD (), m = RD ();   for  (unsigned  i (1 ); i <= n; ++i) a[i][i] = m + 1 ;   for  (unsigned  i (1 ); i <= m; ++i) A = RD (), B = RD (), a[A][B] = max (a[A][B], i);   for  (unsigned  i (n); i; --i) {     for  (unsigned  j (1 ), *J (a[j]); j <= i; J = a[++j]) for  (unsigned  k (1 ); k <= n; ++k)       J[k] = max (J[k], min (J[i], a[i][k]));     for  (unsigned  j (i + 1 ), *J (a[j]); j <= n; J = a[++j]) for  (unsigned  k (1 ); k <= i; ++k)       J[k] = max (J[k], min (J[i], a[i][k]));   }   for  (unsigned  i (1 ); i <= n; ++i) for  (unsigned  j (1 ); j <= i; ++j) ++Prt[min (a[i][j], a[j][i])];   for  (unsigned  i (m); i; --i) Prt[i] += Prt[i + 1 ];   for  (unsigned  i (0 ); i <= m; ++i) printf ("%u " , Prt[i + 1 ]);   putchar (0x0A );   return  Wild_Donkey; } 
显然这个 O ( n 3 ) O(n^3) O ( n 3 ) m m m n ( n − 1 ) 2 \dfrac {n(n - 1)}2 2 n ( n − 1 )  
我们先考虑一个 O ( n m 2 ) O(nm^2) O ( n m 2 ) i i i ( i , j ) (i, j) ( i , j ) j j j < i < i < i i i i 
接下来考虑优化, 因为从后往前加边的时候, 已经到达的点一定还会到达, 所以我们仍然是倒着加边, 考虑每次加边 ( u , v ) (u, v) ( u , v ) i i i 
如果 i i i v v v v v v u u u 
如果 i i i u u u v v v v v v v v v 
如果 i i i u u u v v v u u u 
这个过程要求我们外层枚举点, 内层倒着枚举边, 对正反图都进行这样的操作. 维护两个邻接矩阵共同位置以得到答案. 由于枚举到每个点搜索时每条边只会被经过一次, 因此复杂度为 O ( n m ) O(nm) O ( n m ) 
− 1 -1 − 1 发现不在 P P P P P P P P P P P P 0 0 0 
显然策略是能收就收, 我们贪心地拿就能得到最优答案. 问题转化为找路径上的最长的从 1 1 1 O ( n q ) O(nq) O ( n q ) 2 5 ′ 25' 2 5 ′ 
接下来考虑优化, 我们发现路径分为两部分, 上升段和下降段. 对于上升段, 我们可以预处理从点 i i i 2 j 2^j 2 j k k k i i i 2 j 2^j 2 j k k k O ( log  n ) O(\log n) O ( log  n ) O ( n c log  n ) O(nc\log n) O ( n c log  n ) O ( q log  n + n c log  n ) O(q\log n + nc \log n) O ( q log  n + n c log  n ) 5 0 ′ 50' 5 0 ′ 
对于链的情况, 有从左到右和从右到左两种询问, 由于问题是对称的, 我们只考虑其中一种. 我们离线所有询问, 按结束位置排序, 然后对序列用 map 以匹配长度为键值进行扫描. 对于两个结尾相同的子串, 如果它们匹配长度相等, 我们可以贪心地保留起点靠后的, 又因为两个结尾相同的子串, 结尾靠左的一定包含结尾靠右的所有子序列, 因此匹配长度一定随左端点向右而减小. 所以右端点移动一位只会修改一个元素. 每次查询这个右端点的询问时, 我们二分匹配长度, 使得左端点在查询范围之内即可. 这样可以做到 O ( ( n + q ) log  n ) O((n + q)\log n) O ( ( n + q ) log  n ) 7 0 ′ 70' 7 0 ′ 
现在思考 10 0 ′ 100' 1 0 0 ′ 
对于上升段, 我们在 DFS 回溯的过程中用最长匹配长度为键值的 map 维护从起点向上走到这个点为某个匹配长度时的每个询问. 由于会存在不同的询问匹配长度相等的情况, 我们用并查集合并相同的询问, 然后在维护 map 的同时对并查集同步记录匹配长度.
我们现在把所有上升段走完了, 每个询问的 LCA 上存储上升段的匹配长度, 然后进行 DFS 回答询问. 仍然是 map 加并查集. 我们在回溯的时候回滚两个数据结构, 这样每个时刻存储的就是某个点所有祖先的询问的信息了, 每次到达一个终点就查询并查集回答询问即可.
两次 DFS 每个点只会更新一个元素, 并且每个询问也只会被查询 2 2 2 O ( q ) O(q) O ( q ) O ( ( n + q ) log  n ) O((n + q)\log n) O ( ( n + q ) log  n ) 
看了一下题解, 发现倍增同样可以处理上升段, 下降段的处理和前面自己胡出来的一样.
首先每个点处理一个指针 B e g i Beg_i B e g i  i i i 1 1 1 i i i U p i , j Up_{i, j} U p i , j  i i i w i + 2 j w_i + 2^j w i  + 2 j U p Up U p L a s t i Last_i L a s t i  i i i 
接着是下降段, 重新审视 map 的作用, 它只是用来查询等待特定颜色的询问工具, 而颜色的范围只有 50000 50000 5 0 0 0 0 map.
总复杂度为 O ( n + q ( log  m + log  q + log  n ) ) O(n + q(\log m + \log q + \log n)) O ( n + q ( log  m + log  q + log  n ) ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 unsigned  m, n, c, q;unsigned  A, B, C, D, t;unsigned  Rk[50005 ], Ans[200005 ], Tmp (0 );struct  Set {  Set* Fa;   unsigned  Col, Size; }Mer[200005 ], *Root[50005 ]; inline  Set* Find  (Set* x)    while  (x->Fa != x) x = x->Fa;   return  x; } pair<Set*, Set> Stack[2000005 ], *STop (Stack); pair<Set**, Set*> Stack2[2000005 ], *STop2 (Stack2); inline  Set* Merge  (Set *x, Set *y)    if (!x) return  y;   if (!y) return  x;   if (x->Size < y->Size) swap (x, y);   *(++STop) = {x, *x}, *(++STop) = {y, *y};   x->Size += y->Size, y->Fa = x;   return  x; } struct  Node  {  vector<Node*> E;   Node *Beg, *Up[17 ], *Fa[19 ];   vector<pair<unsigned , unsigned > > Frm;   vector<unsigned > To;   unsigned  Col, Dep;   inline  void  Init ()    inline  void  DFS ()  }N[200005 ], *Last[50005 ]; inline  void  Node::Init  ()    Beg = Last[1 ], Up[0 ] = Last[Col + 1 ];   for  (unsigned  i (0 ); Up[i]; ++i) Up[i + 1 ] = Up[i]->Up[i];   for  (unsigned  i (0 ); Fa[i]; ++i) Fa[i + 1 ] = Fa[i]->Fa[i];   Node* TmpL;   for  (auto  i:E) if (i != Fa[0 ]) {     TmpL = Last[i->Col], Last[i->Col] = i;     i->Fa[0 ] = this , i->Dep = Dep + 1 , i->Init ();     Last[i->Col] = TmpL;   } } inline  void  Node::DFS  ()    pair<Set*, Set> *BackUp (STop)  ;   pair<Set**, Set*> *BackUp2 (STop2)  ;   for  (auto  i:Frm) {     Set *&Point (Root[i.first])  ;     *(++STop2) = {&Point, Point};     if (!Point) Point = Mer + i.second;     else  Point = Merge (Point, Mer + i.second);     Point->Col = i.first;   }   if (Root[Col]) {     (*(++STop2)) = {Root + Col + 1 , Root[Col + 1 ]};     (*(++STop2)) = {Root + Col, Root[Col]};     Root[Col + 1 ] = Root[Col + 1 ] ? Merge (Find (Root[Col]), Find (Root[Col + 1 ])) : Root[Col];     (*(++STop)) = {Root[Col + 1 ], *Root[Col + 1 ]};     Root[Col] = NULL , Root[Col + 1 ]->Col = Col + 1 ;   }   for  (auto  i:To) Ans[i] = Find (Mer + i)->Col;   for  (auto  i:E) if (i != Fa[0 ]) i->DFS ();   while  (STop > BackUp) *(STop->first) = STop->second, --STop;   while  (STop2 > BackUp2) *(STop2->first) = STop2->second, --STop2; } inline  Node* LCA (Node* x, Node* y)    if (x->Dep < y->Dep) swap (x, y);   for  (unsigned  i (17 ); ~i; --i)     if ((x->Fa[i]) && (x->Fa[i]->Dep >= y->Dep)) x = x->Fa[i];   if (x == y) return  x;   for  (unsigned  i (17 ); ~i; --i) if (x->Fa[i] != y->Fa[i]) x = x->Fa[i], y = y->Fa[i];   return  x->Fa[0 ]; } signed  main ()    n = RD (), memset (Rk, 0 , ((m = RD ()) + 1 ) << 2 ), c = RD ();    for  (unsigned  i (1 ); i <= c; ++i) Rk[RD ()] = i;   for  (unsigned  i (1 ); i <= n; ++i) N[i].Col = Rk[RD ()];   for  (unsigned  i (1 ); i < n; ++i)     A = RD (), B = RD (), N[A].E.push_back (N + B), N[B].E.push_back (N + A);   Last[N[1 ].Col] = N + 1 ;   N[1 ].Fa[0 ] = NULL , N[1 ].Dep = 1 , N[1 ].Init ();   Last[N[1 ].Col] = NULL ;   memset (Ans, 0 , ((q = RD ()) + 1 ) << 2 );   for  (unsigned  i (1 ); i <= q; ++i) {     Node *S (N + RD()) , *T (N + RD()) , *L_CA (LCA(S, T))  ;     T->To.push_back (i), S = S->Beg;     if (S && (S->Dep > L_CA->Dep)) {       Ans[i] = 1 ;       for  (unsigned  j (15 ); ~j; --j)         if ((S->Up[j]) && (S->Up[j]->Dep > L_CA->Dep))           S = S->Up[j], Ans[i] += (1  << j);     }     L_CA->Frm.push_back ({Mer[i].Col = (Ans[i] + 1 ), i});   }   for  (unsigned  i (1 ); i <= q; ++i) Mer[i].Fa = Mer + i;   N[1 ].DFS ();   for  (unsigned  i (1 ); i <= q; ++i) printf ("%u\n" , Ans[i] - 1 );   return  Wild_Donkey; } 
发现滚榜的顺序倒过来恰恰就是最后的排名, 只要求出可行的滚榜顺序数量即可. 另外是方案 b b b m m m m m m 
所以每个可行的方案可以通过某个策略构造出一个确定的方案, 如果当前要滚的人编号小于等于当前的第一名, 那么它需要的 b b b b b b b b b b b b max 即为这一次的 b b b 
因此一个朴素的做法是记录当前已经滚榜的人的集合 S S S i i i b i = j b_i = j b i  = j k k k f S , i , j , k f_{S, i, j, k} f S , i , j , k  O ( 2 n n m 2 ) O(2^nnm^2) O ( 2 n n m 2 ) 2.6 ∗ 1 0 10 2.6*10^{10} 2 . 6 ∗ 1 0 1 0 
我们发现由于滚榜时 b b b b b b b b b a a a f S , i , j f_{S, i, j} f S , i , j  S S S i i i m m m O ( 2 n n m ) O(2^nnm) O ( 2 n n m ) O ( n ) O(n) O ( n ) O ( 2 n n 2 m ) O(2^nn^2m) O ( 2 n n 2 m ) 
f S + k , k , j − ( n − ∣ S ∣ ) max  ( a i − a k + [ i < k ] , 0 ) + = f S , i , j f_{S + k, k, j - (n - |S|)\max(a_i - a_k + [i < k], 0)} += f_{S, i, j}
 f S + k , k , j − ( n − ∣ S ∣ ) max ( a i  − a k  + [ i < k ] , 0 )  + = f S , i , j  
注意这个时候不要用 unordered_map, 因为后面的第三维就十分密集了, 用数组可以痛快地通过此题. (注释部分是 unordered_map 写法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 unsigned  f[8200 ][13 ][501 ];unsigned  Pop[8200 ], Cost[13 ][13 ], a[13 ], m, n, N, Mx (0 );unsigned  long  long  Ans (0 ) unsigned  A, B, C, D, t;unsigned  Cnt (0 ) , Tmp (0 ) signed  main ()    N = (1  << (n = RD ())) - 1 , m = RD ();    for  (unsigned  i (0 ); i < n; ++i) {a[i] = RD (); if (a[i] > a[Mx]) Mx = i;}   for  (unsigned  i (0 ); i < n; ++i) for  (unsigned  j (0 ); j < n; ++j) {     Cost[i][j] = a[i] - a[j] + (i < j);     Cost[i][j] = (Cost[i][j] > 0x3f3f3f3f ) ? 0  : Cost[i][j];   }   for  (unsigned  i (0 ), j; i < n; ++i) if ((j = n * Cost[Mx][i]) <= m) f[1  << i][i][m - j] = 1 ;   for  (unsigned  i (0 ); i < N; ++i) {     unsigned  Pi (n - (Pop[i] = Pop[i >> 1 ] + (i & 1 )))      for  (unsigned  j (0 ); j < n; ++j) if ((i >> j) & 1 ) {       for  (unsigned  k (0 ); k < n; ++k) if (!((i >> k) & 1 )) {         unsigned  Use (Pi * Cost[j][k])          for  (unsigned  l (Use); l <= m; ++l) f[i + (1  << k)][k][l - Use] += f[i][j][l];       }     }   }   for  (unsigned  i (0 ); i < n; ++i) for  (unsigned  j (0 ); j <= m; ++j) Ans += f[N][i][j];   printf ("%llu\n" , Ans);   return  Wild_Donkey; } 
看了一下题解, 这道题还可以爆搜滚 6 6 6 7 7 7 
0 0 0 显然加边后支配关系只能解除而不能增加, 支配关系有传递性, 即如果 a a a b b b b b b c c c a a a c c c 
由此我们推出假设一个点的支配集为 S S S S i S_i S i  { S 1 , S 2 , . . . , S i − 1 } \{S_1, S_2,...,S_{i - 1}\} { S 1  , S 2  , . . . , S i − 1  } S i S_i S i  S i + 1 S_{i + 1} S i + 1  
考虑如何找出这个结构.
惊喜地发现 n ≤ 3000 n \leq 3000 n ≤ 3 0 0 0 m ≤ 6000 m \leq 6000 m ≤ 6 0 0 0 1 1 1 O ( n m ) O(nm) O ( n m ) 
1 2 3 4 5 6 7 8 9 10 11 for  (unsigned  i (1 ); i <= m; ++i)  A = RD (), B = RD (), N[A].E.push_back (N + B), N[B].IE.push_back (N + A); for  (unsigned  i (2 ); i <= n; ++i) N[1 ].Control.push_back (N + i), ++(N[i].Bectrl);for  (unsigned  i (2 ); i <= n; ++i) {  for  (unsigned  j (1 ); j <= n; ++j) N[j].Ava = 0 ;   N[i].Ava = 1 , N[1 ].DFS ();   for  (unsigned  j (1 ); j <= n; ++j) if (!(N[j].Ava))     N[i].Control.push_back (N + j), ++(N[j].Bectrl); } for  (unsigned  i (1 ); i <= n; ++i) for  (auto  j:N[i].Control)  if (j->Bectrl == N[i].Bectrl + 1 ) T[i].Son.push_back (T + (j - N)); 
对于每个询问考虑加边的情况, 发现起点和它的祖先的支配集不会有任何变化.
如果终点是起点的祖先或儿子, 那么任何点的支配情况不会有任何变化.
如果加边, 则支配一个点的点只会减少不会增加.
但是有变化的时候我们却不知如何快速找到变化的点.
事后看题解发现建的树确实是支配树, 但是建出支配树我也不会做, 而支配树甚至有 Polylog 的建法, 怒学.
支配树 (Dominator Tree)
 
由于一些原因, 支配树的中文条目和英文条目有一些出入, 不同的地方在中文词条是不能自恰的, 在这里提醒一下. 写这段文字时的版本是这个 (可能要复制链接访问而不是单击访问) , 表格中 5 5 5 5 5 5 2 2 2 2 2 2 
对于 i i i j j j i i i j j j i i i i i i F a i Fa_i F a i  i i i 
我们一开始从 1 1 1 
定义 y y y x x x y y y x x x x x x y y y i i i D F N i > D F N x DFN_i > DFN_x D F N i  > D F N x  S e m i Sem_i S e m i  i i i 
每个点 DFS 树上的父亲一定是它的一个 semi. 这样就可以推出 S e m i Sem_i S e m i  i i i S e m i Sem_i S e m i  i i i i i i i i i S e m i Sem_i S e m i  i i i i i i S e m i Sem_i S e m i  
如果存在边 x → y x \rightarrow y x → y D F N x < D F N y DFN_x < DFN_y D F N x  < D F N y  x x x y y y x x x y y y 
如果 D F N x > D F N y DFN_x > DFN_y D F N x  > D F N y  x x x D F N i > D F N y DFN_i > DFN_y D F N i  > D F N y  i i i S e m Sem S e m y y y 
设点 i i i x x x D F N i > D F N y DFN_i > DFN_y D F N i  > D F N y  i i i x x x x → y x \rightarrow y x → y i i i y y y S e m i Sem_i S e m i  i i i j j j D F N j > D F N i DFN_j > DFN_i D F N j  > D F N i  S e m i Sem_i S e m i  y y y S e m i Sem_i S e m i  y y y x → y x \rightarrow y x → y j j j D F N j > D F N y DFN_j > DFN_y D F N j  > D F N y  y y y 
推论可以简化为: x x x y y y x x x x x x S e m Sem S e m 
为了求出每个点的 S e m Sem S e m S e m Sem S e m S e m Sem S e m S e m Sem S e m S e m Sem S e m S e m Sem S e m O ( log  n ) O(\log n) O ( log  n ) 
F a i Fa_i F a i  S e m i Sem_i S e m i  F a i Fa_i F a i  i i i i i i 
当 S e m i Sem_i S e m i  i i i i i i F a i Fa_i F a i  S e m i Sem_i S e m i  
当 S e m i Sem_i S e m i  i i i S e m i Sem_i S e m i  i i i F a i Fa_i F a i  S e m i Sem_i S e m i  
我们找到 S e m i Sem_i S e m i  i i i S e m i Sem_i S e m i  S e m Sem S e m j j j 
如果能够找到路径绕开 S e m i Sem_i S e m i  D F N S e m j < D F N S e m i DFN_{Sem_j} < DFN_{Sem_i} D F N S e m j   < D F N S e m i   S e m j = S e m i Sem_j = Sem_i S e m j  = S e m i  D F N F a i ≥ D F N S e m i DFN_{Fa_i} \geq DFN_{Sem_i} D F N F a i   ≥ D F N S e m i   F a i Fa_i F a i  S e m i Sem_i S e m i  F a i = S e m i Fa_i = Sem_i F a i  = S e m i  
如果真的可以绕开, 那么支配 i i i j j j S e m j Sem_j S e m j  S e m i Sem_i S e m i  j j j i i i F a i = F a j Fa_i = Fa_j F a i  = F a j  
我们发现在求 S e m Sem S e m D F N S e m i DFN_{Sem_i} D F N S e m i   i i i x x x S e m Sem S e m x x x S e m Sem S e m x x x S e m Sem S e m S e m y = x Sem_y = x S e m y  = x y y y y y y j j j 
最后通过每个点的 j j j S e m Sem S e m F a Fa F a 
最后是代码, 这就是面向 vector 编程.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 unsigned  m, n;unsigned  A, B, C, D, t;unsigned  Cnt (0 ) , Ans (0 ) , Tmp (0 ) struct  Node ;struct  Set  {  Set* Fa;   Node* Val; }S[200005 ], *Stack[200005 ], **STop (Stack); struct  Node  {  vector<Node*> E, IE, DFSSon, SemSon, Son;   Node *Sem, *Fa, *J;   unsigned  DFN, Size;   inline  void  DFSforDFN ()    inline  void  DFSforSize ()    inline  char  Les (Node* x)  return  Sem->DFN < x->Sem->DFN;} }N[200005 ], *Rk[200005 ]; inline  Node* Find (Set* x)    while  (x != x->Fa) *(++STop) = x, x = x->Fa;   Node* Cur (x->Val)  ;   while  (STop > Stack)     Cur = ((*STop)->Val = (Cur->Les ((*STop)->Val)) ? Cur : (*STop)->Val), (*(STop--))->Fa = x;   return  Cur; } inline  void  Node::DFSforDFN ()    Rk[DFN = ++Cnt] = this ;   for  (auto  i:E) if (!(i->DFN)) DFSSon.push_back (i), i->DFSforDFN (); } inline  void  Node::DFSforSize ()    Size = 1 ;   for  (auto  i:Son) i->DFSforSize (), Size += i->Size; } signed  main ()    n = RD (), m = RD ();    for  (unsigned  i (1 ); i <= m; ++i)     A = RD (), B = RD (), N[A].E.push_back (N + B), N[B].IE.push_back (N + A);    N[1 ].DFSforDFN (), N[n + 1 ].DFN = 0x3f3f3f3f ;   for  (unsigned  i (1 ); i <= n; ++i) S[i] = {S + i, N + i};   for  (unsigned  i (1 ); i <= n; ++i) N[i].J = N + i;   for  (unsigned  i (n); i; --i) {     Node* Cur (Rk[i])  ;     for  (auto  j:Cur->SemSon) {       Node* Get (Find(S + (j - N)))  ;       if (Get->Les (j->J)) j->J = Get;     }     Cur->Sem = N + n + 1 ;     for  (auto  j:Cur->IE) {       if (j->DFN < Cur->DFN) Cur->Sem = (Cur->Sem->DFN > j->DFN) ? j : Cur->Sem;       else  {         Node* Get (Find (S + (j - N)));         Cur->Sem = (Get->Les (Cur)) ? Get->Sem : Cur->Sem;       }     }     Cur->Sem->SemSon.push_back (Cur);     for  (auto  j:Cur->DFSSon) S[j - N].Fa = S + (Cur - N);   }   for  (unsigned  i (1 ); i <= n; ++i) {     Node* Cur (Rk[i])  ;     if (Cur->J->Sem == Cur->Sem) Cur->Fa = Cur->Sem;     else  Cur->Fa = Cur->J->Fa;   }   for  (unsigned  i (2 ); i <= n; ++i) N[i].Fa->Son.push_back (N + i);   N[1 ].DFSforSize ();   for  (unsigned  i (1 ); i <= n; ++i) printf ("%u " , N[i].Size); putchar (0x0A );   return  Wild_Donkey; } 
显然即使 O ( 1 ) O(1) O ( 1 ) 
一个点的受支配集在支配树上就是这个点和它的所有祖先, 我们要找的是连边后可以找到一条不全是一个点 x x x 1 1 1 x x x 
我们发现一个点的支配情况发生改变, 它在支配树上的子树的所有点的受支配集一定都改变, 这是因为一个点的祖先也是它子树中所有点的祖先. 因此只要找出所有 x x x F a x Fa_x F a x  1 1 1 x x x 
为了找出这种路径, 对于每个点 x x x F a x Fa_x F a x  A c c e s s i b l e x Accessible_x A c c e s s i b l e x  A c c e s s i b l e x Accessible_x A c c e s s i b l e x  y y y x x x y y y A v a i l a b l e y Available_y A v a i l a b l e y  
对于每个询问 x → y x \rightarrow y x → y A v a i l a b l e y Available_y A v a i l a b l e y  i ∈ A v a i l a b l e y i \in Available_y i ∈ A v a i l a b l e y  1 1 1 x x x F a i Fa_i F a i  i i i 1 1 1 x x x F a i Fa_i F a i  x x x 
我们 O ( n ) O(n) O ( n ) x x x i ∈ A v a i l a b l e y i \in Available_y i ∈ A v a i l a b l e y  F a i Fa_i F a i  i i i O ( n ) O(n) O ( n ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 unsigned  m, n, q;unsigned  A, B, C, D, t;unsigned  Cnt (0 ) , Ans (0 ) , Tmp (0 ) struct  Node  {  vector<Node*> E, IE, Control;   unsigned  Bectrl;   char  Ava;   inline  void  DFS ()       Ava = 1 ;     for  (auto  i:E) if (!(i->Ava)) i->DFS ();   }   inline  void  IDFS ()       Ava = 1 ;     for  (auto  i:IE) if (!(i->Ava)) i->IDFS ();   } }N[3005 ]; struct  Tree  {  bitset<3005> Available;   vector <Tree*> Son;   Tree *Fa;   unsigned  DFN, Size;   char  Ava;   inline  void  Init ()       DFN = ++Cnt, Size = 1 ;     for  (auto  i:Son) i->Fa = this , i->Init (), Size += i->Size;   } }T[3005 ]; signed  main ()    n = RD (), m = RD (), q = RD ();      T[1 ].Init ();   for  (unsigned  i (2 ); i <= n; ++i) {     for  (unsigned  j (1 ); j <= n; ++j) N[j].Ava = 0 ;     N[T[i].Fa - T].Ava = 1 , N[i].IDFS (), N[T[i].Fa - T].Ava = 0 ;     for  (unsigned  j (1 ); j <= n; ++j) if (N[j].Ava) T[j].Available[i] = 1 ;   }   int  Delta[3005 ];   for  (unsigned  i (1 ); i <= q; ++i) {     Tree *Fr (T + RD()) , *To (T + RD()) , *TmpF (Fr)  ;     while  (Fr) Fr->Ava = 1 , Fr = Fr->Fa;     memset (Delta, 0 , (n + 1 ) << 2 ), Ans = 0 ;     for  (unsigned  i (2 ); i <= n; ++i) if (To->Available[i] && (!(T[i].Fa->Ava))) ++Delta[T[i].DFN], --Delta[T[i].DFN + T[i].Size];     int  Cur (0 )      for  (unsigned  i (1 ); i <= n; ++i) {if (Cur + Delta[i]) ++Ans; Cur += Delta[i];}     Fr = TmpF; while  (Fr) Fr->Ava = 0 , Fr = Fr->Fa;     printf ("%u\n" , Ans);   }   return  Wild_Donkey; }